<template>
	<view class="compress" v-if="canvasId">
		<canvas :canvas-id="canvasId" :style="{ width: canvasSize.width, height: canvasSize.height }"></canvas>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				pic: '',
				canvasSize: {
					width: 0,
					height: 0
				},
				canvasId: ''
			};
		},
		mounted() {
			// 创建 canvasId
			if (!uni || !uni._helang_compress_canvas) {
				uni._helang_compress_canvas = 1;
			} else {
				uni._helang_compress_canvas++;
			}
			this.canvasId = `compress-canvas${uni._helang_compress_canvas}`;
		},
		methods: {
			// 压缩
			compressFun(params) {
				return new Promise(async (resolve, reject) => {
					// 等待图片信息
					let info = await this.getImageInfo(params.src)
						.then((info) => info)
						.catch(() => null);

					if (!info) {
						reject('获取图片信息异常');
						return;
					}

					// 设置最大 & 最小 尺寸
					const maxSize = params.maxSize || 1080;
					const minSize = params.minSize || 640;

					// 当前图片尺寸
					let {
						width,
						height
					} = info;

					// 非 H5 平台进行最小尺寸校验
					// #ifndef H5
					if (width <= minSize && height <= minSize) {
						resolve(params.src);
						return;
					}
					// #endif

					// 最大尺寸计算
					//（图像的宽度和高度是否超过最大尺寸。如果其中任一维度超过最大尺寸，代码将对图像进行调整，以使其适应最大尺寸并保持其宽高比。）
					// 这样可以确保图像在调整大小后仍保持原始比例，并且不会超过指定的最大尺寸

					if (width > maxSize || height > maxSize) {
						if (width > height) {
							height = Math.floor(height / (width / maxSize));
							width = maxSize;
						} else {
							width = Math.floor(width / (height / maxSize));
							height = maxSize;
						}
					}

					// 设置画布尺寸
					this.$set(this, 'canvasSize', {
						width: `${width}px`,
						height: `${height}px`
					});

					// Vue.nextTick 回调在 App 有异常，则使用 setTimeout 等待DOM更新
					setTimeout(() => {
						// 创建 canvas 绘图上下文（指定 canvasId）。在自定义组件下，第二个参数传入组件实例this，以操作组件内 <canvas/> 组件
						// Tip: 需要指定 canvasId，该绘图上下文只作用于对应的 <canvas/>
						const ctx = uni.createCanvasContext(this.canvasId, this);
						// 清除画布上在该矩形区域内的内容。（x，y，宽，高）
						ctx.clearRect(0, 0, width, height);
						// 绘制图像到画布。（所要绘制的图片资源，x，y，宽，高）
						ctx.drawImage(info.path, 0, 0, width, height);
						// 将之前在绘图上下文中的描述（路径、变形、样式）画到 canvas 中。
						// 本次绘制是否接着上一次绘制，即reserve参数为false，则在本次调用drawCanvas绘制之前native层应先清空画布再继续绘制；若reserver参数为true，则保留当前画布上的内容，本次调用drawCanvas绘制的内容覆盖在上面，默认 false
						// 绘制完成后回调
						ctx.draw(false, () => {
							// 把当前画布指定区域的内容导出生成指定大小的图片，并返回文件路径。在自定义组件下，第二个参数传入自定义组件实例，以操作组件内 <canvas> 组件。
							uni.canvasToTempFilePath({
									x: 0, //画布x轴起点（默认0）
									y: 0, //画布y轴起点（默认0）
									width: width, //画布宽度（默认为canvas宽度-x）
									height: height, //画布高度（默认为canvas高度-y
									destWidth: width, //图片宽度（默认为 width * 屏幕像素密度）
									destHeight: height, //输出图片高度（默认为 height * 屏幕像素密度）
									canvasId: this
										.canvasId, //画布标识，传入 <canvas/> 的 canvas-id（支付宝小程序是id、其他平台是canvas-id）
									fileType: params.fileType === 'webp' ? 'jpg' : params
										.fileType ||
										'png', //目标文件的类型，只支持 'jpg' 或 'png'。默认为 'png'
									quality: params.quality ||
										0.9, //图片的质量，取值范围为 (0, 1]，不在范围内时当作1.0处理
									success: (res) => {
										// H5 平台下，通过 canvas.toDataURL() 直接获取 WebP
										const canUseWebP = typeof document !==
											'undefined' ||
											(uni.canIUse(
												'canvasToTempFilePath.fileType.webp'
											));
										if (params.fileType === 'webp' &&
											canUseWebP) {
											const canvas = document.getElementById(this
												.canvasId) || uni.canIUse(
												'canvasToTempFilePath.fileType.webp'
											);
											if (canvas && canvas
												.toDataURL) { // 确保 API 存在
												const webpBase64 = canvas.toDataURL(
													'image/webp', params.quality ||
													0.9);
												resolve(webpBase64);
											} else {
												reject(new Error(
												'H5 环境不支持 toDataURL'));
											}
										} else {
											// 其他情况走原逻辑
											// 在H5平台下，tempFilePath 为 base64
											if (params.resType == 'blob' || params
												.resType == 'file' || params.resType ==
												'obj')
												//需要二进制流调用该方法拿到
												resolve(this.base64To(res
													.tempFilePath, params.resType
												));
											if (params
												.resType == 'ArrayBuffer') resolve(uni
												.base64ToArrayBuffer(res
													.tempFilePath
													.split(',')[1]));
											resolve(res.tempFilePath);
										}
									},
									fail: (err) => {
										reject(null);
									}
								},
								this
							);
						});
					}, 300);
				});
			},
			// base64转blob,file,obj
			base64To(base64, type = 'blob') {
				var arr = base64.split(','),
					mime = arr[0].match(/:(.*?);/)[1],
					bstr = atob(arr[1]),
					n = bstr.length,
					u8arr = new Uint8Array(n);
				while (n--) {
					u8arr[n] = bstr.charCodeAt(n);
				}
				const BlobUrl = new Blob([u8arr], {
					type: mime,
				});
				if (type == 'blob') return BlobUrl;
				var img = new File([BlobUrl], uni.$uv.guid() + '.' + mime.split('\/').pop(), {
					type: mime
				})
				if (type == 'file') return img;
				if (type == 'obj') return window.URL.createObjectURL(img);

			},
			// 获取图片信息
			getImageInfo(src) {
				return new Promise((resolve, reject) => {
					uni.getImageInfo({
						src,
						success: (info) => {
							resolve(info);
						},
						fail: (err) => {
							console.log(err, 'err===获取图片信息');
							reject(null);
						}
					});
				});
			},
			// 批量压缩
			async compress(params) {
				// 初始化状态变量
				let [index, done, fail] = [0, 0, 0];
				let paths = [];

				// 处理待压缩图片列表
				let waitList = Array.isArray(params.src) ? params.src : [params.src];

				// 批量压缩方法
				let batch = async () => {
					while (index < waitList.length) {
						try {
							const path = await next();
							done++;
							paths.push(path);
							params.progress?.({
								done,
								fail,
								count: waitList.length
							});
						} catch (error) {
							fail++;
							params.progress?.({
								done,
								fail,
								count: waitList.length
							});
						}
						index++;
					}
				};

				// 单个图片压缩方法
				let next = () => {
					const currentSrc = waitList[index];
					return this.compressFun({
						src: currentSrc,
						maxSize: params.maxSize,
						fileType: params.fileType,
						quality: params.quality,
						minSize: params.minSize,
						resType: params.resType || '',
					});
				};
				// 返回Promise并处理结果
				return new Promise((resolve, reject) => {
					try {
						batch()
							.then(() => {
								if (typeof params.src === 'string') {
									resolve(paths[0]);
								} else {
									resolve(paths);
								}
							})
							.catch((error) => {
								reject(error);
							});
					} catch (error) {
						reject(error);
					}
				});
			}
		}
	};
</script>

<style lang="scss" scoped>
	.compress {
		position: fixed;
		width: 12px;
		height: 12px;
		overflow: hidden;
		top: -99999px;
		left: 0;
	}
</style>